using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Beaker.FileSystem;
using Beaker.Threading;

namespace OpenCube.IO.Overload
{
	internal class OverloadingFolder
		: OverloadingEntry<IFolder>, IFolder
	{
		internal OverloadingFolder(OverloadingFolder parent, IEnumerable<IFolder> folders)
			: base(parent, folders)
		{
			Contract.Requires<ArgumentNullException>(folders != null, "At least one folder is required.");
			Contract.Requires(folders.Any(), "At least one folder is required.");
		}

		/// <summary>
		/// Gets the files in this folder.
		/// </summary>
		/// <returns>
		/// A sequence of files.
		/// </returns>
		public IEnumerable<IFile> GetFiles()
		{
			return
				from IFolder folder in Entries
				from IFile file in folder.GetFiles()
				where file != null
				// group by does keep source order. yes!
				// http://stackoverflow.com/questions/1452507/linq-to-objects-does-groupby-preserve-order-of-elements
				// http://msdn.microsoft.com/en-us/library/bb534304.aspx
				group file by file.Name.ToUpperInvariant()
					into fileGroups
					select new OverloadingFile(this, fileGroups);
		}

		public Task<IEnumerable<IFile>> GetFilesAsync(CancellationToken cancellationToken)
		{
			var source = new TaskCompletionSource<IEnumerable<IFile>>();
			return source.RunSynchronous(GetFiles, cancellationToken);
		}

		/// <summary>
		/// Gets the file with the given name.
		/// </summary>
		/// <param name="fileName">The name of the file to get.</param>
		/// <returns>The file with the given name, or <c>null</c> when it could not be found.</returns>
		public IFile GetFile(string fileName)
		{
			return
				new OverloadingFile(
					this,
					from IFolder folder in Entries
					where folder != null
					let file = folder.GetFile(fileName)
					where file != null
					select file
					);
		}

		public Task<IFile> GetFileAsync(string fileName, CancellationToken cancellationToken)
		{
			var source = new TaskCompletionSource<IFile>();
			return source.RunSynchronous(GetFile, fileName, cancellationToken);
		}

		public IFile CreateFile(string fileName)
		{
			throw new NotSupportedException("Overloading file sytem is read-only");
		}

		public Task<IFile> CreateFileAsync(string fileName, CancellationToken cancellationToken)
		{
			throw new NotSupportedException("Overloading file sytem is read-only");
		}

		public IFile GetOrCreateFile(string fileName)
		{
			throw new NotSupportedException("Overloading file sytem is read-only");
		}

		public Task<IFile> GetOrCreateFileAsync(string fileName, CancellationToken cancellationToken)
		{
			throw new NotSupportedException("Overloading file sytem is read-only");
		}

		/// <summary>
		/// Gets the folders in this folder.
		/// </summary>
		/// <returns>
		/// A sequence of folders.
		/// </returns>
		public IEnumerable<IFolder> GetFolders()
		{
			return
				from IFolder folder in Entries
				from IFolder subFolder in folder.GetFolders()
				where subFolder != null
				// group by does keep source order. yes!
				// http://stackoverflow.com/questions/1452507/linq-to-objects-does-groupby-preserve-order-of-elements
				// http://msdn.microsoft.com/en-us/library/bb534304.aspx
				group subFolder by subFolder.Name.ToUpperInvariant()
					into folderGroups
					select new OverloadingFolder(this, folderGroups);
		}

		public Task<IEnumerable<IFolder>> GetFoldersAsync(CancellationToken cancellationToken)
		{
			var source = new TaskCompletionSource<IEnumerable<IFolder>>();
			return source.RunSynchronous(GetFolders, cancellationToken);
		}

		/// <summary>
		/// Gets the folder with the given name.
		/// </summary>
		/// <param name="folderName">The name of the folder to get.</param>
		/// <returns>The folder with the given name, or <c>null</c> when it could not be found.</returns>
		public IFolder GetFolder(string folderName)
		{
			return
				new OverloadingFolder(
					this,
					from IFolder folder in Entries
					where folder != null
					select folder.GetFolder(folderName)
					);
		}

		public Task<IFolder> GetFolderAsync(string folderName, CancellationToken cancellationToken)
		{
			var source = new TaskCompletionSource<IFolder>();
			return source.RunSynchronous(GetFolder, folderName, cancellationToken);
		}

		public IFolder CreateFolder(string folderName)
		{
			throw new NotSupportedException("Overloading file sytem is read-only");
		}

		public Task<IFolder> CreateFolderAsync(string folderName, CancellationToken cancellationToken)
		{
			throw new NotSupportedException("Overloading file sytem is read-only");
		}

		public IFolder GetOrCreateFolder(string folderName)
		{
			throw new NotSupportedException("Overloading file sytem is read-only");
		}

		public Task<IFolder> GetOrCreateFolderAsync(string folderName, CancellationToken cancellationToken)
		{
			throw new NotSupportedException("Overloading file sytem is read-only");
		}

		/// <summary>
		/// Gets the entires in this folder.
		/// </summary>
		/// <returns>
		/// A sequence of items.
		/// </returns>
		public IEnumerable<IFileSystemEntry> GetEntries()
		{
			throw new NotImplementedException();
		}

		public Task<IEnumerable<IFileSystemEntry>> GetEntriesAsync(CancellationToken cancellationToken)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Gets the entry with the given name in this folder.
		/// </summary>
		/// <param name="entryName">Name of the entry to get.</param>
		/// <returns>The entry with the given name, or <c>null</c> when it could not be found.</returns>
		public IFileSystemEntry GetEntry(string entryName)
		{
			throw new NotImplementedException();
		}

		public Task<IFileSystemEntry> GetEntryAsync(string entryName, CancellationToken cancellationToken)
		{
			throw new NotImplementedException();
		}
	}
}